package jschool.homework.connection.pool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class BlockingQueueConnectionPool implements ConnectionPool {

	private final BlockingQueue<PooledConnection> connectionPool;
	private final String url;
	private volatile int availableConnections;
	private volatile int aliveConnections;
	private final int maxConnections;
	private volatile boolean closed;
	
	BlockingQueueConnectionPool(String connectionUrl, int maxConnections) throws SQLException {
		this.url = connectionUrl;
		connectionPool = new LinkedBlockingQueue<>(maxConnections);
		this.maxConnections = maxConnections;
		aliveConnections = 0;
		availableConnections = 0;
	}
	
	@Override
	public PooledConnection getConnection() throws InterruptedException, SQLException {
		return getConnection(Long.MAX_VALUE);
	}
	
	@Override
	public synchronized PooledConnection getConnection(long timeout) throws InterruptedException, SQLException {
		if (closed) {
			throw new IllegalStateException("Connection Pool is closed");
		}
		
		while (availableConnections == 0) {
			if (aliveConnections < maxConnections) {
				connectionPool.offer(createNewPooledConnection());
				availableConnections++;
			} else {
				wait(timeout);
				if (availableConnections == 0) {
					return null;
				}
			}
		}
		availableConnections--;
		return connectionPool.take();
	}
	
	@Override
	public synchronized void releaseConnection(PooledConnection connection) {
		if (closed) 
			return;
		
		if (!connection.setActive(false)) {
			// Try to release connection that has already been released
			System.out.println("Connection is released or closed");
			return;
		}
		
		try {
			connection.rollback();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		connectionPool.add(connection);
		availableConnections++;
		notify();
	}
	
	private synchronized PooledConnection createNewPooledConnection() throws SQLException {
		try {
			PooledConnection conn = new PooledConnection(createConnection());
			conn.addConnectionEventListener(this);
			conn.setActive(true);
			return conn;
		} catch (SQLException e) {
			aliveConnections++;
			throw e;
		}
	}
	
	protected Connection createConnection() throws SQLException {
		return DriverManager.getConnection(url);
	}

	@Override
	public synchronized void connectionClosed() {
		if (closed)
			return;
		
		aliveConnections--;
		System.out.println("Connection has been closed");
		
		try {
			connectionPool.offer(createNewPooledConnection());
		} catch (SQLException e) {
			System.err.println("Can't create new pooled connection in place of closed");
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void close() {
		if (closed)
			return;
		
		closed = true;
		List<PooledConnection> connPool = new ArrayList<>();
		connectionPool.drainTo(connPool);
		for (PooledConnection conn : connPool) {
			try {
				conn.removeConnectionEventListener(this);
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	
}
